#include "ExampleApplication.h"
#include "ExampleFrameListener.h"
#include "OgrePlatform.h"
#include "windows.h"
#include "Ogre.h"

using namespace Ogre;

#define ATMOSPHERE_MATERIAL_NAME		"AtmosphereSphereMaterial"
//#define DEBUG_PLANE

extern void createSphere(const std::string& strName, const float r, const int nRings = 16, const int nSegments = 16);
extern void createOpticalDepthTexture(int width, int height);

SceneNode* _sphereNode;
SceneNode* _sunNode;
float _sunRotate = 0;
float _g = 0.76f;
GpuProgramParametersSharedPtr _atmosphereParams;

class TestFrameListener : public ExampleFrameListener
{
	SceneManager* mSceneMgr;
public:
	TestFrameListener(SceneManager* scnMgr, RenderWindow* win, Camera* cam)
		: ExampleFrameListener(win, cam), mSceneMgr(scnMgr)
	{
	}

	virtual bool frameStarted(const FrameEvent& evt)
	{
		_sphereNode->setPosition(mCamera->getPosition());
		_sunNode->setPosition(mCamera->getPosition() + Vector3(0, 50 * cosf(_sunRotate),  50 * sinf(_sunRotate)));
		return true;
	}
#ifdef DEBUG_PLANE
	virtual bool frameEnded(const FrameEvent& evt)
	{
		ExampleFrameListener::frameEnded(evt);

		static int nFrames = 0;

		if (nFrames++ == 2)
		{
			std::ostringstream ss;
			ss << "screenshot_" << ++mNumScreenShots << ".png";
			mWindow->writeContentsToFile(ss.str());
			mTimeUntilNextToggle = 0.5;
			mDebugText = "Saved: " + ss.str();
		}

		return true;
	}
#endif
	void update_g(float g)
	{
		Vector4 v4_g;
		v4_g.x = 3 * (1.f - g*g) / (2 * (2.f + g*g));
		v4_g.y = 1.f + g*g;
		v4_g.z = -2.f*g;
		v4_g.w = 1.f;
		_atmosphereParams->setNamedConstant("f4CS_g", v4_g);
	}
	virtual bool processUnbufferedKeyInput(const FrameEvent& evt)
	{
		if (!ExampleFrameListener::processUnbufferedKeyInput(evt))
			return false;
		if (mKeyboard->isKeyDown(OIS::KC_C) && mTimeUntilNextToggle <= 0)
		{
			mTimeUntilNextToggle = 1;
		}
		else if (mKeyboard->isKeyDown(OIS::KC_1) && mTimeUntilNextToggle <= 0)
		{
			_sunNode->flipVisibility();
			mTimeUntilNextToggle = 0.5;
		}
		else if (mKeyboard->isKeyDown(OIS::KC_2) && mTimeUntilNextToggle <= 0)
		{
			// enable/disable Rayleigh inscattering
			static bool rayleigh_enabled = true;
			rayleigh_enabled = !rayleigh_enabled;
			if (rayleigh_enabled)
			{
				_atmosphereParams->setNamedConstant("f3RayleighExtCoeff", Vector3(5.804543e-6f, 1.356291e-5f, 3.311258e-5f));
				_atmosphereParams->setNamedConstant("f3AngularRayleighSctrCoeff", Vector3(3.464332e-007f, 8.094767e-007f, 1.976261e-006f));
			}
			else
			{
				_atmosphereParams->setNamedConstant("f3RayleighExtCoeff", Vector3::ZERO);
				_atmosphereParams->setNamedConstant("f3AngularRayleighSctrCoeff", Vector3::ZERO);
			}
			mTimeUntilNextToggle = 0.5;
		}
		else if (mKeyboard->isKeyDown(OIS::KC_3) && mTimeUntilNextToggle <= 0)
		{
			// enable/disable Mie inscattering
			static bool mie_enabled = true;
			mie_enabled = !mie_enabled;
			if (mie_enabled)
			{
				_atmosphereParams->setNamedConstant("f3MieExtCoeff", Vector3(2.200000e-005f, 2.200000e-005f, 2.200000e-005f));
				_atmosphereParams->setNamedConstant("f3AngularMieSctrCoeff", Vector3(1.591549e-006f, 1.591549e-006f, 1.591549e-006f));
			}
			else
			{
				_atmosphereParams->setNamedConstant("f3MieExtCoeff", Vector3::ZERO);
				_atmosphereParams->setNamedConstant("f3AngularMieSctrCoeff", Vector3::ZERO);
			}
			mTimeUntilNextToggle = 0.5;
		}
		else if (mKeyboard->isKeyDown(OIS::KC_4) && mTimeUntilNextToggle <= 0)
		{
			if (_g >= -0.98f)
			{
				_g -= 0.01f;
				update_g(_g);
			}
			mTimeUntilNextToggle = 0.1;
		}
		else if (mKeyboard->isKeyDown(OIS::KC_5) && mTimeUntilNextToggle <= 0)
		{
			if (_g <= 0.98f)
			{
				_g += 0.01f;
				update_g(_g);
			}
			mTimeUntilNextToggle = 0.1;
		}

		return true;
	}

	virtual bool processUnbufferedMouseInput(const FrameEvent& evt)
	{
		if (mKeyboard->isKeyDown(OIS::KC_LCONTROL))
		{
			const OIS::MouseState &ms = mMouse->getMouseState();
			_sunRotate -= ms.Y.rel * 0.01f;
			_atmosphereParams->setNamedConstant("sunDir", Vector3(0, cos(_sunRotate), sin(_sunRotate)));
		}
		else
			ExampleFrameListener::processUnbufferedMouseInput(evt);
		return true;
	}
};

class TestApplication : public ExampleApplication
{
public:
	TestApplication() : ExampleApplication()
	{
	}

protected:
	virtual void createFrameListener(void)
	{
		mFrameListener= new TestFrameListener(mSceneMgr, mWindow, mCamera);
		mFrameListener->showDebugOverlay(true);
        mRoot->addFrameListener(mFrameListener);
	}

	virtual void createScene(void)
	{
		createOpticalDepthTexture(256, 64);
		MaterialPtr atmosphereMaterial = MaterialManager::getSingleton().getByName(ATMOSPHERE_MATERIAL_NAME);
		Pass* pass = atmosphereMaterial->getTechnique(0)->getPass(0);
		TextureUnitState* tus0 = pass->getTextureUnitState(0);
		tus0->setTextureName("OpticalDepthTexture");
		tus0->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
		
		_atmosphereParams = pass->getFragmentProgramParameters();

		createSphere("mySphereMesh", 1);
		Entity* sphereEntity = mSceneMgr->createEntity("mySphereEntity", "mySphereMesh");
		_sphereNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
		sphereEntity->setMaterialName(ATMOSPHERE_MATERIAL_NAME);
		_sphereNode->attachObject(sphereEntity);
		_sphereNode->setPosition(0, 0, 0);
		_sphereNode->setScale(100, 100, 100);

		createSphere("SunMesh", 1);
		Entity* sunEntity = mSceneMgr->createEntity("sunEntity", "SunMesh");
		sunEntity->setMaterialName("BaseWhiteNoLighting");
		_sunNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
		_sunNode->attachObject(sunEntity);
		_sunNode->setPosition(0, 50, 0);
#ifdef DEBUG_PLANE
		MaterialPtr planeMaterial = MaterialManager::getSingleton().getByName("PlaneMaterial");
		tus0 = planeMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0);
		tus0->setTextureName("OpticalDepthTexture");
		tus0->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);

		Plane plane(Vector3::UNIT_Z, 0);
		MeshPtr planeMesh = MeshManager::getSingleton().createPlane("TexturePlane", "General", 
			plane, 25.6f, 6.4f, 1, 1, true, 1, 1.0f, 1.0f, Vector3::UNIT_Y);
		SceneNode* planeNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
		//SceneNode* planeNode = _sphereNode->createChildSceneNode();
		Entity* planeEnt = mSceneMgr->createEntity("myPlaneEntity", "TexturePlane");
		planeEnt->setMaterialName("PlaneMaterial");
		//planeEnt->setMaterialName("BaseWhiteNoLighting");
		planeNode->attachObject(planeEnt);
		planeNode->setPosition(0, 0, -40);
#endif		
		mCamera->setPosition(0, 0, 0);
		mCamera->lookAt(0, 0, -1);
		mCamera->setNearClipDistance(1);
		mCamera->setFarClipDistance(1000);
	}
};

INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR cmdLine, INT)
{
	int argc = __argc;
	char** argv = __argv;
	try
	{
		TestApplication testApp;
		testApp.go();
	}
	catch (Ogre::Exception& e)
	{
		MessageBoxA(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_ICONERROR | MB_TASKMODAL);
	}
	return 0;
}